﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Rockfan.Framework.Audio;
using Rockfan.Framework.Graphics;

namespace Rockfan.Framework.Entities
{
    public sealed class Drumboard : Playerboard
    {
        public Drumboard(PlayerIndex index, NoteTracks chart)
            : base(index, chart)
        {
            this.BoardSpeed = 1.0f;
            this.EnableUpdates = true;
            this.EnableDrawing = true;
            this.IsFiveNoteBoard = true;

            Notes = new List<PlayNote>();
            Buttons = new List<Fretbutton>();
            BeatMarkers = new List<Beatmarker>();
        }

        public override void LoadContent()
        {
            SetupBoard();
            SetupCamera();
            SetupNoteContent();

            BeatMarkerTexture = new Texture2D(EngineServices.GraphicsDevice, 1, 1);
            BeatMarkerTexture.SetData<Color>(new Color[] { Color.White });
        }

        public override async Task LoadContentAsync(ProgressChanged callback)
        {
            // [TODO] Load content here.

            await Task.Run(() => { });            
        }

        public override void Start()
        {
            
        }

        public override void Finish()
        {
            
        }

        public override void SwapNotes(float delta)
        {
            NotesSwapped = !NotesSwapped;

            foreach (var note in Notes)
                note.Swap(delta);

            foreach (var button in Buttons)
                button.Swap(delta);
        }

        public override void UpdateBoardPosition(float delta)
        {
            
        }
        
        public override void ChangeDifficulty(Difficulty difficulty)
        {
            
        }

        protected override void Update(float delta)
        {
            var distance = (delta * 5 * BoardSpeed);

            Fretboard.BottomTextureCoordinate += delta;
            Fretboard.TopTextureCoordinate = Fretboard.BottomTextureCoordinate + 2.0f;
            Fretboard.Update(delta, true);

            #region Add Beats

            if (LastBeatTime != 0.0f)
            {
                TotalBeatTime += delta;

                if (TotalBeatTime - LastBeatTime >= 0.125f)
                {
                    var offset = TotalBeatTime - LastBeatTime - 0.125f;

                    if (TotalBeats % 16 == 0)
                        AddBeatMarker(MarkerType.Measure, offset);

                    else if (TotalBeats % 4 == 0)
                        AddBeatMarker(MarkerType.Beat, offset);

                    else if (TotalBeats % 2 == 0)
                        AddBeatMarker(MarkerType.Eigth, offset);

                    LastBeatTime = TotalBeatTime;
                    TotalBeats++;

                    AddNote((NoteType)Math.Pow(2.0f, (TotalBeats % 5)), offset);
                }
            }
            else LastBeatTime = delta;

            #endregion

            #region Update Markers

            for (int i = BeatMarkers.Count - 1; i >= 0; i--)
            {
                var marker = BeatMarkers[i];
                marker.Alpha = (5.0f - marker.Y) * 0.33f;

                if (marker.MarkerType == MarkerType.Eigth && marker.Alpha > .4f)
                    marker.Alpha = 0.4f;

                else if (marker.MarkerType == MarkerType.Beat && marker.Alpha > 0.75f)
                    marker.Alpha = 0.75f;

                if (marker.Y - distance > -5.5f)
                {
                    marker.Y -= distance;
                    marker.UpdateState(delta);
                }
                else BeatMarkers.RemoveAt(i);
            }

            #endregion

            #region Update Notes

            for (int i = Notes.Count - 1; i >= 0; i--)
            {
                var note = Notes[i];
                note.Alpha = (5.0f - note.Y) * 0.33f;

                if (note.Y - distance > -5.5f)
                {
                    note.Y -= distance;
                    note.UpdateState(delta);
                }
                else Notes.RemoveAt(i);
            }

            #endregion

            foreach (var button in Buttons)
                button.UpdateState(delta);

            foreach (var note in Notes)
                note.UpdateState(delta);

            
        }

        protected override void Draw()
        {
            FretboardEffect.Parameters["View"].SetValue(Camera.View);
            FretboardEffect.Parameters["Projection"].SetValue(Camera.Projection);

            SpriteEffect.Parameters["View"].SetValue(Camera.View);
            SpriteEffect.Parameters["Projection"].SetValue(Camera.Projection);

            EngineServices.GraphicsDevice.Viewport = Camera.Viewport;

            foreach (var pass in FretboardEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                Fretboard.Draw(FretboardEffect);              
            }
            
            foreach (var pass in SpriteEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                foreach (var marker in BeatMarkers)
                    marker.Draw(SpriteEffect);
            }

            foreach (var pass in FretboardEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                Sidebars.Draw(FretboardEffect);
            }

            foreach (var pass in SpriteEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                
                foreach (var button in Buttons)
                    button.Draw(SpriteEffect);

                foreach (var note in Notes)
                    note.Draw(SpriteEffect);
            }
        }

        protected override void Destroy()
        {
            
        }

        private void SetupBoard()
        {
            var topTextureCoord = 0.0f;
            var bottomTextureCoord = 1.0f;
            var boardItemYScale = 5.0f;
            var boardItemXScale = 0.985f;

            var texture = Theme.GetTextureFromName("DefaultBoard").Texture;
            var centerLineTexture = Theme.GetTextureFromName("Centerlines").Texture;
            var sidebarTexture = Theme.GetTextureFromName("Sidebars").Texture;

            Fretboard = new Fretboard(texture);
            Sidebars = new Fretboard(sidebarTexture);

            Fretboard.TopTextureCoordinate = topTextureCoord;
            Fretboard.BottomTextureCoordinate = bottomTextureCoord;
            Fretboard.XScale = boardItemXScale;
            Fretboard.YScale = boardItemYScale;
            Fretboard.Initialize();

            Sidebars.TopTextureCoordinate = topTextureCoord;
            Sidebars.BottomTextureCoordinate = bottomTextureCoord;
            Sidebars.XScale = boardItemXScale * 1.03f;
            Sidebars.YScale = boardItemYScale;
            Sidebars.Initialize();


            AddButton(FretButtonType.Red);
            AddButton(FretButtonType.Yellow);
            AddButton(FretButtonType.Blue);
            AddButton(FretButtonType.Green);

            if (IsFiveNoteBoard)
                AddButton(FretButtonType.Orange);
        }

        private void SetupCamera()
        {
            var positionY = 0;
            var playerIndex = (int)PlayerIndex;
            var playerCount = 1;

            var width = EngineServices.ClientWidth;
            var height = EngineServices.ClientHeight;
            var clientWidth = EngineServices.ClientWidth;
            width = (int)(width * .45f);

            var position = (float)playerIndex * width;
            var positionOffset = ((1 - (width / (clientWidth / (float)playerCount))) * clientWidth) * 0.5f;

            if (playerCount > 1)
            {
                var halfOffset = positionOffset * 0.5f;
                positionOffset += (-halfOffset + (halfOffset * playerIndex));
            }

            position += positionOffset;

            Camera.Viewport = new Viewport(new Rectangle((int)position, positionY, width, height - positionY));

            Camera.AspectRatio = 1.092f;
            Camera.FieldOfView = 30.0f;
            Camera.Y = -8.3f;
            Camera.Z = 1.4f;
            Camera.XRotation = 82.0f;
        }

        private void SetupNoteContent()
        {
            GreenNote = Theme.GetTextureFromName("GreenNote");
            GreenNoteHopo = Theme.GetTextureFromName("GreenNoteHopo");
            GreenNoteTapo = Theme.GetTextureFromName("GreenNoteTapo");

            RedNote = Theme.GetTextureFromName("RedNote");
            RedNoteHopo = Theme.GetTextureFromName("RedNoteHopo");
            RedNoteTapo = Theme.GetTextureFromName("RedNoteTapo");

            YellowNote = Theme.GetTextureFromName("YellowNote");
            YellowNoteHopo = Theme.GetTextureFromName("YellowNoteHopo");
            YellowNoteTapo = Theme.GetTextureFromName("YellowNoteTapo");

            BlueNote = Theme.GetTextureFromName("BlueNote");
            BlueNoteHopo = Theme.GetTextureFromName("BlueNoteHopo");
            BlueNoteTapo = Theme.GetTextureFromName("BlueNoteTapo");

            OrangeNote = Theme.GetTextureFromName("OrangeNote");
            OrangeNoteHopo = Theme.GetTextureFromName("OrangeNoteHopo");
            OrangeNoteTapo = Theme.GetTextureFromName("OrangeNoteTapo");

            DeadNote = Theme.GetTextureFromName("DeadNote");
            PowerNote = Theme.GetTextureFromName("PowerNote");
            OverdriveNote = Theme.GetTextureFromName("OverdriveNote");
        }

        private void AddButton(FretButtonType buttonType)
        {
            var button = new Fretbutton(buttonType);
            var isGreen = (button.ButtonType & FretButtonType.Green) == FretButtonType.Green;
            var isRed = (button.ButtonType & FretButtonType.Red) == FretButtonType.Red;
            var isYellow = (button.ButtonType & FretButtonType.Yellow) == FretButtonType.Yellow;
            var isBlue = (button.ButtonType & FretButtonType.Blue) == FretButtonType.Blue;
            var isOrange = (button.ButtonType & FretButtonType.Orange) == FretButtonType.Orange;

            var scale = 0.133f;

            button.Y = -4.5f;            
            button.YScale = scale;
            button.Z = scale / 2.0f;
            button.XScale = 0.1825f;            
            button.XRotation = 10.0f;

            if (IsFiveNoteBoard)
            {
                if (isRed)
                    button.X = -0.755f;

                else if (isYellow)
                    button.X = -0.375f;

                else if (isOrange)
                    button.X = 0.38f;

                else if (isGreen)
                    button.X = 0.76f;
            }
            else
            {
                if (isRed)
                    button.X = -0.70f;

                else if (isYellow)
                    button.X = -0.233f;

                else if (isBlue)
                    button.X = 0.233f;

                else if (isGreen)
                    button.X = 0.70f;
            }

            if (isGreen)
            {
                button.NormalTexture = Theme.GetTextureFromName("GreenFret");
                button.HitTexture = Theme.GetTextureFromName("GreenFretHit");
                button.PressedTexture = Theme.GetTextureFromName("GreenFretPressed");
            }
            else if (isRed)
            {
                button.NormalTexture = Theme.GetTextureFromName("RedFret");
                button.HitTexture = Theme.GetTextureFromName("RedFretHit");
                button.PressedTexture = Theme.GetTextureFromName("RedFretPressed");
            }
            else if (isYellow)
            {
                button.NormalTexture = Theme.GetTextureFromName("YellowFret");
                button.HitTexture = Theme.GetTextureFromName("YellowFretHit");
                button.PressedTexture = Theme.GetTextureFromName("YellowFretPressed");
            }
            else if (isBlue)
            {
                button.NormalTexture = Theme.GetTextureFromName("BlueFret");
                button.HitTexture = Theme.GetTextureFromName("BlueFretHit");
                button.PressedTexture = Theme.GetTextureFromName("BlueFretPressed");
            }
            else if (isOrange)
            {
                button.NormalTexture = Theme.GetTextureFromName("OrangeFret");
                button.HitTexture = Theme.GetTextureFromName("OrangeFretHit");
                button.PressedTexture = Theme.GetTextureFromName("OrangeFretPressed");
            }

            Buttons.Add(button);
        }

        private void AddBeatMarker(MarkerType markerType, float offset)
        {
            // Because it would take to long to try and retrive the texture for the beat marker every time the maker
            // needs to be added, lets store a local copy
            var beat = new Beatmarker(markerType, BeatMarkerTexture);
            beat.Alpha = 0.0f;
            beat.XScale = 0.995f;
            beat.XRotation = Camera.XRotation;
            
            // Once audio is supported in this object, this should be the correct formula
            //beat.Y = (5.1f * (AudioTrack.Delay * BoardSpeed) - AudioTrack.Delay + 1) - offset;
            beat.Y = (5.1f * BoardSpeed) - offset;

            switch(markerType)
            {
                case MarkerType.Beat:
                    beat.YScale = .0066f;
                    break;

                case MarkerType.Eigth:
                    beat.YScale = .005f;                    
                    break;

                case MarkerType.Measure:
                    beat.YScale = .01f;
                    break;
            }

            beat.Z = -beat.YScale / 2.0f;
            BeatMarkers.Add(beat);           
        }

        private void AddNote(NoteType noteType, float offset)
        {
            if ((noteType & NoteType.Orange) == NoteType.Orange && !IsFiveNoteBoard)
                return;

            var note = new PlayNote(noteType);
            var isHopo = note.IsHopo;
            var isTapo = note.IsTapo;

            var swapValue = NotesSwapped ? -1.0f : 1.0f;
            var isGreen = (note.NoteType & NoteType.Green) == NoteType.Green;
            var isRed = (note.NoteType & NoteType.Red) == NoteType.Red;
            var isYellow = (note.NoteType & NoteType.Yellow) == NoteType.Yellow;
            var isBlue = (note.NoteType & NoteType.Blue) == NoteType.Blue;
            var isOrange = (note.NoteType & NoteType.Orange) == NoteType.Orange;

            note.Alpha = 0.0f;
            note.YScale = 0.1f;
            note.XScale = 0.1875f;
            note.Y = (5.0f * BoardSpeed) - offset; // note.Y = 5f * ((AudioTrack.Delay * boardSpeed) - AudioTrack.Delay + 1) - offset;
            note.Z = note.YScale / 2.0f;
            note.XRotation = 45.0f;
            note.DeadTexture = DeadNote;
            note.PowerTexture = PowerNote;
            note.OverdriveTexture = OverdriveNote;
            Notes.Add(note);

            if (IsFiveNoteBoard)
            {
                if (isRed)
                    note.X = -0.755f * swapValue;

                else if (isYellow)
                    note.X = -0.375f * swapValue;

                else if (isOrange)
                    note.X = 0.38f * swapValue;

                else if (isGreen)
                    note.X = 0.76f * swapValue;
            }
            else
            {
                if (isRed)
                    note.X = -0.70f * swapValue;

                else if (isYellow)
                    note.X = -0.233f * swapValue;

                else if (isBlue)
                    note.X = 0.233f * swapValue;

                else if (isGreen)
                    note.X = 0.70f * swapValue;
            }

            #region Green Note Setup

            if (isGreen)
            {
                if (isHopo)
                    note.NormalTexture = GreenNoteHopo;

                else if (isTapo)
                    note.NormalTexture = GreenNoteTapo;

                else note.NormalTexture = GreenNote;
            }

            #endregion

            #region Red Note Setup

            else if (isRed)
            {
                if (isHopo)
                    note.NormalTexture = RedNoteHopo;

                else if (isTapo)
                    note.NormalTexture = RedNoteTapo;

                else note.NormalTexture = RedNote;
            }

            #endregion

            #region Yellow Note Setup

            else if (isYellow)
            {
                if (isHopo)
                    note.NormalTexture = YellowNoteHopo;

                else if (isTapo)
                    note.NormalTexture = YellowNoteTapo;

                else note.NormalTexture = YellowNote;
            }

            #endregion

            #region Blue Note Setup

            else if (isBlue)
            {
                if (isHopo)
                    note.NormalTexture = BlueNoteHopo;

                else if (isTapo)
                    note.NormalTexture = BlueNoteTapo;

                else note.NormalTexture = BlueNote;
            }

            #endregion

            #region Orange Note Setup

            else if (isOrange)
            {
                if (isHopo)
                    note.NormalTexture = OrangeNoteHopo;

                else if (isTapo)
                    note.NormalTexture = OrangeNoteTapo;

                else note.NormalTexture = OrangeNote;
            }

            #endregion
        }

        private bool NotesSwapped;
        private bool IsFiveNoteBoard;

        private int TotalBeats;
        private float LastBeatTime;
        private float TotalBeatTime;

        private float BoardSpeed;
        //private AudioTrack AudioTrack;        

        private Fretboard Sidebars;
        private Fretboard Fretboard;

        private Texture2D BeatMarkerTexture;

        private List<PlayNote> Notes;
        private List<Fretbutton> Buttons;
        private List<Beatmarker> BeatMarkers;        

        #region Note Textures

        TextureInfo GreenNote;
        TextureInfo GreenNoteHopo;
        TextureInfo GreenNoteTapo;

        TextureInfo RedNote;
        TextureInfo RedNoteHopo;
        TextureInfo RedNoteTapo;

        TextureInfo YellowNote;
        TextureInfo YellowNoteHopo;
        TextureInfo YellowNoteTapo;

        TextureInfo BlueNote;
        TextureInfo BlueNoteHopo;
        TextureInfo BlueNoteTapo;

        TextureInfo OrangeNote;
        TextureInfo OrangeNoteHopo;
        TextureInfo OrangeNoteTapo;

        TextureInfo DeadNote;
        TextureInfo PowerNote;
        TextureInfo OverdriveNote;        

        #endregion
    }
}
